﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Threading;
using IniFileParser.Model;
using Microsoft.EntityFrameworkCore;
using MilkTeaMachine.OfflineClient.EntityFrameworkCore;
using MilkTeaMachine.OfflineClient.EntityFrameworkCore.Models;
using MilkTeaMachine.OfflineClient.Models;
using MilkTeaMachine.OfflineClient.Utils;
using NModbus;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Spire.Xls;
using Splat;

namespace MilkTeaMachine.OfflineClient.ViewModels;

public class AutoRunningScanOrderViewModel : ViewModelBase, IRoutableViewModel
{
    private readonly ApplicationDbContext _db;

    public AutoRunningScanOrderViewModel(IScreen hostScreen)
    {
        _db = Locator.Current.GetService<ApplicationDbContext>()!;
        HostScreen = hostScreen;
        AutoRunningPressKey = ReactiveCommand.CreateFromObservable(
            () => hostScreen.Router.Navigate.Execute(new AutoRunningPressKeyViewModel(HostScreen)));
        DoItCmd = ReactiveCommand.CreateFromTask(async () => await DoItAsync());
        EnterProductCodeCmd =
            ReactiveCommand.CreateFromTask(EnterProductCodeAsync);
        this.WhenAnyValue(
                vm => vm.FormulaCode,
                productCode => !string.IsNullOrWhiteSpace(productCode?.Trim()))
            .Throttle(TimeSpan.FromMilliseconds(300))
            .DistinctUntilChanged()
            .Select(_ => Unit.Default)
            .InvokeCommand(EnterProductCodeCmd);
    }

    public AutoRunningScanOrderViewModel() : this(default!)
    {
    }

    private async Task EnterProductCodeAsync()
    {
        if (string.IsNullOrWhiteSpace(FormulaCode)) return;
        //读取excel配方
        var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Formulas", "配方.xlsx");
        if (!File.Exists(path))
        {
            await Dispatcher.UIThread.InvokeAsync(() => _ = MessageBoxUtil.GetBox("配方文件不存在").ShowWindowAsync());
            return;
        }

        var excelFormulaDto = await GetFormula(path, FormulaCode);
        if (excelFormulaDto is null)
            return;
        ToMakeFormulaData = excelFormulaDto;
        var ingredientIdList = excelFormulaDto.IngredientIdWeightDictionary.Keys;
        _ingredientIdWeightDictionary = excelFormulaDto.IngredientIdWeightDictionary;
        _ingredientDictionary = await _db.Ingredients
            .Where(ingredient => ingredientIdList.Contains(ingredient.Id))
            .ToDictionaryAsync(ingredient => ingredient.Id);

        StringBuilder sb = new(excelFormulaDto.IngredientIdWeightDictionary.Keys.Count + 3);
        var i = 0;
        foreach (var formulaIngredientIdAndWeight in excelFormulaDto.IngredientIdWeightDictionary)
        {
            var ingredient = _ingredientDictionary[formulaIngredientIdAndWeight.Key];
            if (++i % 2 == 0)
                sb.AppendLine($"{ingredient.IngredientName}：{formulaIngredientIdAndWeight.Value}克\t");
            else
                sb.Append($"{ingredient.IngredientName}：{formulaIngredientIdAndWeight.Value}克\t");
        }

        sb.AppendLine();
        if (++i % 2 == 0)
        {
            sb.AppendLine($"糖量：{excelFormulaDto.SugarType}\t");
            sb.Append($"冰量：{excelFormulaDto.IceType}\t");
            sb.Append($"杯型：{excelFormulaDto.CupType}\t");
        }

        else
        {
            sb.Append($"糖量：{excelFormulaDto.SugarType}\t");
            sb.AppendLine($"冰量：{excelFormulaDto.IceType}\t");
            sb.Append($"杯型：{excelFormulaDto.CupType}\t");
        }

        await Dispatcher.UIThread.InvokeAsync(() =>
        {
            FormulaName = $"{excelFormulaDto.FormulaName}({excelFormulaDto.SugarType})";
            FormulaCodeDisplay = excelFormulaDto.FormulaCode;
            FormulaCode = string.Empty;
            FormulaDetail = sb.ToString();
        });
    }

    private async Task<ExcelFormulaDto?> GetFormula(string path, string formulaCode)
    {
        var workBook = new Workbook();
        workBook.LoadFromFile(path);
        var formulaDataTable = workBook.Worksheets[0].ExportDataTable();
        var formulaRow = formulaDataTable.Rows.Cast<DataRow>()
            .LastOrDefault(dr => dr["配方代码"].ToString()?.Equals(formulaCode) is true);
        if (formulaRow is null)
        {
            await Dispatcher.UIThread.InvokeAsync(() => _ = MessageBoxUtil.GetBox("配方不存在").ShowWindowAsync());
            return null;
        }

        var dto = new ExcelFormulaDto
        {
            FormulaName = formulaRow["配方名称"].ToString() ?? "配方名称为空",
            FormulaCode = formulaRow["配方代码"].ToString()!,
            CupType = Enum.Parse<CupTypeEnum>(formulaRow["杯型"].ToString()!),
            SugarType = Enum.Parse<SugarTypeEnum>(formulaRow["糖度"].ToString()!),
            IceType = Enum.Parse<IceTypeEnum>(formulaRow["冰块"].ToString()!),
            IngredientIdWeightDictionary = new Dictionary<int, double>(6)
        };
        for (var i = 1; i < 7; i++)
        {
            var ingredientId = formulaRow?[$"原料{i}编号"].ToString();
            var ingredientWeight = formulaRow?[$"原料{i}重量"].ToString();
            if (string.IsNullOrWhiteSpace(ingredientId) || string.IsNullOrWhiteSpace(ingredientWeight))
                continue;
            if (!double.TryParse(ingredientWeight, out var weight))
            {
                await Dispatcher.UIThread.InvokeAsync(() => _ = MessageBoxUtil.GetBox("配方存在非数字的重量").ShowWindowAsync());
                return null;
            }

            if (!int.TryParse(ingredientId, out var ingredientIdInt))
            {
                await Dispatcher.UIThread.InvokeAsync(() =>
                    _ = MessageBoxUtil.GetBox($"配方存在非数字的原料编号[{ingredientId}]").ShowWindowAsync());
                return null;
            }

            if (!dto.IngredientIdWeightDictionary.TryAdd(ingredientIdInt, weight))
            {
                await Dispatcher.UIThread.InvokeAsync(() =>
                    _ = MessageBoxUtil.GetBox($"配方存在重复的原料编号[{ingredientIdInt}]").ShowWindowAsync());
                return null;
            }
        }

        var ingredientIdList = dto.IngredientIdWeightDictionary.Keys;
        var ingredientList = await _db.Ingredients
            .Where(ingredient => ingredientIdList.Contains(ingredient.Id))
            .ToListAsync();
        if (ingredientList.Count != ingredientIdList.Count)
        {
            await Dispatcher.UIThread.InvokeAsync(() =>
                _ = MessageBoxUtil.GetBox($"配方原料编号中有不存在的原料编号").ShowWindowAsync());
            return null;
        }

        if (ingredientList.Any(ingredient => ingredient.TurnsPerGram is null or < 1))
        {
            await Dispatcher.UIThread.InvokeAsync(() =>
                _ = MessageBoxUtil.GetBox("配方存在未校准的原料").ShowWindowAsync());
            return null;
        }

        var hotWaterWeight = formulaRow!["热水重量"].ToString();
        var coldWaterWeight = formulaRow["冷水重量"].ToString();
        var iceWeight = formulaRow["冰块重量"].ToString();
        List<string?> validateList = [hotWaterWeight, coldWaterWeight, iceWeight];
        if (validateList.Count(s => s?.Equals("N", StringComparison.OrdinalIgnoreCase) is true) > 1)
        {
            await Dispatcher.UIThread.InvokeAsync(
                () => _ = MessageBoxUtil.GetBox("配方冷水、热水、冰块重量存在多个N").ShowWindowAsync());
            return null;
        }

        var notPass = validateList
            .FirstOrDefault(s =>
                s?.Equals("N", StringComparison.OrdinalIgnoreCase) is false &&
                !double.TryParse(s, out _));
        if (notPass is not null)
        {
            await Dispatcher.UIThread.InvokeAsync(() =>
                _ = MessageBoxUtil.GetBox($"配方存在非数字的{notPass}").ShowWindowAsync());
            return null;
        }

        dto.HotWaterWeight = hotWaterWeight ?? string.Empty;
        dto.ColdWaterWeight = coldWaterWeight ?? string.Empty;
        dto.IceWeight = iceWeight ?? string.Empty;
        return dto;
    }


    private Task DoItAsync()
    {
        HostScreen.Router.Navigate.Execute(new HourGlassViewModel(HostScreen, DoItAsyncDelegate));
        return Task.CompletedTask;

        async Task DoItAsyncDelegate()
        {
            if (ToMakeFormulaData is null || _ingredientDictionary is null || _ingredientIdWeightDictionary is null)
            {
                await Dispatcher.UIThread.InvokeAsync(() => _ = MessageBoxUtil.GetBox("请先输入产品代码").ShowWindowAsync());
                return;
            }

            var parser = new IniFileParser.IniFileParser();
            var data = parser.ReadFile("Configuration/config.ini");
            using var master = IModbusSerialFactory.Create();
            var totalWeight = 0.0;
            var waitAddressArray = new List<byte>(_ingredientIdWeightDictionary.Count);
            foreach (var formulaIngredientIdAndWeight in _ingredientIdWeightDictionary)
            {
                //获取地址
                var ingredient = _ingredientDictionary[formulaIngredientIdAndWeight.Key];
                var speed = Convert.ToUInt16(ingredient.OutSpeed);
                var acceleration = Convert.ToByte(data["Machine"]["acceleration"]);
                var direction = Convert.ToByte(data["Machine"]["direction"]);
                var directionAndAcceleration = App.GetComposedUshort(direction, acceleration);
                //获取转多少圈
                var rotation = ingredient.TurnsPerGram * formulaIngredientIdAndWeight.Value;
                totalWeight += formulaIngredientIdAndWeight.Value;
                var needPulse = Convert.ToUInt32(rotation * 3200);
                var pulseHigh = (ushort)((needPulse >> 16) & 0xFFFF); // 获取高16位
                var pulseLow = (ushort)(needPulse & 0xFFFF); // 获取低16位

                //获取地址对应的地址
                var realAddress = byte.Parse(data["Address"][$"Address{ingredient.DeviceAddress}"]);
                waitAddressArray.Add(realAddress);
                await master.WriteMultipleRegistersAsync(realAddress,
                    // 0x01,
                    0x00FD, [directionAndAcceleration, speed, pulseHigh, pulseLow, 0]);
            }

            //等待完成
            await WaitAllFinishedAsync(waitAddressArray, master).ConfigureAwait(true);
            //额外放入冷水热水冰
            var nDeviceAddress = await AddOtherWeightIngredientAsync(data, parser, master);
            //补足重量
            if (nDeviceAddress != default)
            {
                await AddNDeviceAddressAsync(nDeviceAddress, totalWeight, data, parser, master);
            }
        }
    }

    public static async Task WaitAllFinishedAsync(List<byte> waitAddressArray, IModbusSerialMaster master)
    {
        foreach (var address in waitAddressArray)
        {
            ushort speed;
            do
            {
                var speedArray = await master.ReadInputRegistersAsync(address, 0x35, 2).ConfigureAwait(true);
                if (speedArray is null)
                {
                    await Dispatcher.UIThread.InvokeAsync(() =>
                        _ = MessageBoxUtil.GetBox("等待设备完成超时").ShowWindowAsync());
                    return;
                }

                //获取实时转速
                speed = speedArray[1];
                await Task.Delay(200).ConfigureAwait(true);
            } while (speed != 0);
        }
    }

    private async Task AddNDeviceAddressAsync(int nDeviceAddress, double totalWeight, IniData data,
        IniFileParser.IniFileParser parser, IModbusSerialMaster master)
    {
        var ingredient = await _db.Ingredients.FirstAsync(i => i.DeviceAddress == nDeviceAddress);
        //获取剩余多少重量
        var restWeight = await GetCurrentWeightAsync(data) - totalWeight;
        if (restWeight < 1)
        {
            return;
        }

        var address = ingredient.IngredientName switch
        {
            "热水" => byte.Parse(data["BoolInternalAddress"]["hotWater"]),
            "冰水" => byte.Parse(data["BoolInternalAddress"]["coldWater"]),
            "冰块" => byte.Parse(data["BoolInternalAddress"]["ice"]),
            _ => throw new ArgumentOutOfRangeException()
        };
        //获取转多少秒
        var waitSeconds = Convert.ToDouble(ingredient.TurnsPerGram * restWeight);
        var modbusAddress = Convert.ToByte(data["BoolInternalAddress"]["ModbusAddress"]);
        //打开
        await master.WriteSingleCoilAsync(modbusAddress, address, true).ConfigureAwait(true);
        await Task.Delay(TimeSpan.FromSeconds(waitSeconds * 1));
        //关闭
        await master.WriteSingleCoilAsync(modbusAddress, address, false).ConfigureAwait(true);
    }

    private async Task<double> GetCurrentWeightAsync(IniFileParser.Model.IniData data)
    {
        using var master = IModbusSerialFactory.Create();
        //机内称重
        var weight = await master.ReadHoldingRegistersAsync(0x01, 0x01, 2);
        if (weight is null)
        {
            _ = Dispatcher.UIThread.InvokeAsync(async () =>
            {
                await MessageBoxUtil.GetBox("读取称重失败").ShowWindowAsync().ConfigureAwait(true);
            });
            return 0.00;
        }

        var highBits = BitConverter.GetBytes(weight[0]);
        var lowBits = BitConverter.GetBytes(weight[1]);
        var weightValue = BitConverter.ToInt32([..lowBits, ..highBits]);
        weightValue = Convert.ToInt32(weightValue * Convert.ToDouble(data["Weight"]["Multiple"]));
        return weightValue;
    }

    private async Task<int> AddOtherWeightIngredientAsync(IniFileParser.Model.IniData data,
        IniFileParser.IniFileParser parser, IModbusSerialMaster master)
    {
        int nDeviceAddress = default;
        var ingredientList = await _db.Ingredients
            .Where(i => i.IngredientName == "热水" || i.IngredientName == "冰水" || i.IngredientName == "冰块").ToListAsync()
            .ConfigureAwait(true);
        //冰水、热水称重
        var modbusAddress = Convert.ToByte(data["BoolInternalAddress"]["ModbusAddress"]);
        var coldWaterAddress = Convert.ToByte(data["BoolInternalAddress"]["coldWater"]);
        var hotWaterAddress = Convert.ToByte(data["BoolInternalAddress"]["hotWater"]);
        var iceAddress = Convert.ToByte(data["BoolInternalAddress"]["ice"]);
        double waitSeconds;
        if (!ToMakeFormulaData!.HotWaterWeight!.Equals("N", StringComparison.OrdinalIgnoreCase))
        {
            var hotWaterIngredient = ingredientList.FirstOrDefault(ingredient => ingredient.IngredientName == "热水");
            if (hotWaterIngredient is null)
            {
                await Dispatcher.UIThread.InvokeAsync(() => MessageBoxUtil.GetBox("热水原料不存在"));
                return nDeviceAddress;
            }

            //获取转多少秒
            waitSeconds = Convert.ToDouble(hotWaterIngredient.TurnsPerGram *
                                           Convert.ToDouble(ToMakeFormulaData!.HotWaterWeight));
            //打开
            await master.WriteSingleCoilAsync(modbusAddress, hotWaterAddress, true).ConfigureAwait(false);
            await Task.Delay(TimeSpan.FromSeconds(waitSeconds * 1));
            //关闭
            await master.WriteSingleCoilAsync(modbusAddress, hotWaterAddress, false).ConfigureAwait(false);
            ingredientList.Remove(hotWaterIngredient);
        }

        if (!ToMakeFormulaData!.ColdWaterWeight!.Equals("N", StringComparison.OrdinalIgnoreCase))
        {
            var coldWaterIngredient = ingredientList.FirstOrDefault(ingredient => ingredient.IngredientName == "冰水");
            if (coldWaterIngredient is null)
            {
                await Dispatcher.UIThread.InvokeAsync(() => MessageBoxUtil.GetBox("冰水原料不存在"));
                return nDeviceAddress;
            }

            //获取转多少秒
            waitSeconds = Convert.ToDouble(coldWaterIngredient.TurnsPerGram *
                                           Convert.ToDouble(ToMakeFormulaData!.ColdWaterWeight));
            //打开
            await master.WriteSingleCoilAsync(modbusAddress, coldWaterAddress, true).ConfigureAwait(false);
            await Task.Delay(TimeSpan.FromSeconds(waitSeconds * 1));
            //关闭
            await master.WriteSingleCoilAsync(modbusAddress, coldWaterAddress, false).ConfigureAwait(false);
            ingredientList.Remove(coldWaterIngredient);
        }

        if (!ToMakeFormulaData!.IceWeight!.Equals("N", StringComparison.OrdinalIgnoreCase))
        {
            var iceIngredient = ingredientList.FirstOrDefault(ingredient => ingredient.IngredientName == "冰块");
            if (iceIngredient is null)
            {
                await Dispatcher.UIThread.InvokeAsync(() => MessageBoxUtil.GetBox("冰块原料不存在"));
                return nDeviceAddress;
            }

            //获取转多少秒
            waitSeconds = Convert.ToDouble(iceIngredient.TurnsPerGram *
                                           Convert.ToDouble(ToMakeFormulaData!.ColdWaterWeight));
            //打开
            await master.WriteSingleCoilAsync(modbusAddress, iceAddress, true).ConfigureAwait(true);
            await Task.Delay(TimeSpan.FromSeconds(waitSeconds * 1));
            //关闭
            await master.WriteSingleCoilAsync(modbusAddress, iceAddress, false).ConfigureAwait(true);
            ingredientList.Remove(iceIngredient);
            ingredientList.Remove(iceIngredient);
        }

        return ingredientList.Count != 0 ? ingredientList.First().DeviceAddress : default;
    }

    private Dictionary<int, Ingredient>? _ingredientDictionary;
    private Dictionary<int, double>? _ingredientIdWeightDictionary;
    public string? UrlPathSegment { get; } = nameof(AutoRunningScanOrderViewModel);
    public IScreen HostScreen { get; }
    public ReactiveCommand<Unit, IRoutableViewModel> AutoRunningPressKey { get; }
    public ReactiveCommand<Unit, Unit> EnterProductCodeCmd { get; set; }
    public ReactiveCommand<Unit, Unit> DoItCmd { get; }
    [Reactive] public string FormulaDetail { get; set; } = "配方表格";
    [Reactive] public string? FormulaCode { get; set; }
    [Reactive] public string? FormulaName { get; set; } = "产品名称";
    [Reactive] public string? FormulaCodeDisplay { get; set; } = "产品代码";
    private ExcelFormulaDto? ToMakeFormulaData { get; set; }
}